package function

import (
	"crypto/subtle"
	"encoding/base64"
	"errors"
	"fmt"
	. "go_tempate/common/store"
	. "go_tempate/common/struct"
	. "go_tempate/config"
	"golang.org/x/crypto/argon2"
	"strings"
)

//argon2加密信息参考：https://www.alexedwards.net/blog/how-to-hash-and-verify-passwords-with-argon2-in-go

/*
 注册首次加密

 参数:
  password:需要加密的密码字符串
  sql:是否是入库加密操作，true则执行后台加密；否则是前端加密

 返回:
  encodedHash:加密成功后的字符串内容
  err:若是报错的错误信息，没有的话为nil
*/
// ——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
func GenerateFromPassword(password string, sql bool) (encodedHash string, err error) {
	//声明盐的变量信息
	var salt []byte

	//声明选择加密格式信息
	var format *Argon2Format

	//判断是否开启了sql入库生成
	//----------------------------------------------------------------------------------------------------------
	if sql {
		//随机一个长度为指定长度的字符串作为盐
		salt, err = GenerateRandomBytes(Argon2BG.SaltLength)
		if err != nil {
			return "", err
		}
		//将加密格式统一为后台加密格式
		format = Argon2BG
	} else {
		//未开启则进行使用前端固定加密key
		salt = []byte(PEKEY)
		//固定为前端加密格式
		format = Argon2PE
	}

	//将明文密码、salt和参数传递给argon2.IDKey功能;这将使用Argon2id生成密码的哈希变体。使用的盐值为前端加密的key
	//----------------------------------------------------------------------------------------------------------
	hash := argon2.IDKey([]byte(password), salt, format.Iterations, format.Memory, format.Parallelism, format.KeyLength)

	// Base64对盐值和散列密码进行编码。
	//----------------------------------------------------------------------------------------------------------
	b64Salt := base64.RawStdEncoding.EncodeToString(salt)
	b64Hash := base64.RawStdEncoding.EncodeToString(hash)

	// 使用标准编码的哈希表示形式返回字符串。
	//----------------------------------------------------------------------------------------------------------
	encodedHash = fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, format.Memory, format.Iterations, format.Parallelism, b64Salt, b64Hash)

	return encodedHash, nil
}

/*
 比对

 参数:
   password:需要进行对比的密码字符串
   encodedHash:数据库中已经加密完成后的母字符串

 返回:
   match:对比结果，true为成功；false为失败
   err:若是报错的错误信息，没有的话为nil
*/
// ——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
func ComparePasswordAndHash(password, encodedHash string) (match bool, err error) {
	// 从编码的密码中提取参数、salt和派生密钥
	//----------------------------------------------------------------------------------------------------------
	p, salt, hash, err := DecodeHash(encodedHash)
	if err != nil {
		return false, err
	}

	// 使用相同的参数从另一个密码派生密钥。
	//----------------------------------------------------------------------------------------------------------
	otherHash := argon2.IDKey([]byte(password), salt, p.Iterations, p.Memory, p.Parallelism, p.KeyLength)

	// 检查散列密码的内容是否相同。我们正在使用微妙的.ConstantTimeCompare（）函数,以帮助防止定时攻击。
	//----------------------------------------------------------------------------------------------------------
	if subtle.ConstantTimeCompare(hash, otherHash) == 1 {
		return true, nil
	}
	return false, nil
}

/*
 解析哈希

 参数:
   encodedHash:加密后的字符串

 返回:
   p:解析后的返回格式
   salt:解析后的盐值
   hash:解析后的hash 字节数组
   err:若是报错的错误信息，没有的话为nil
*/
// ——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
func DecodeHash(encodedHash string) (p *Argon2Format, salt, hash []byte, err error) {
	vals := strings.Split(encodedHash, "$")
	if len(vals) != 6 {
		return nil, nil, nil, errors.New(ErrInvalidHash)
	}

	var version int
	_, err = fmt.Sscanf(vals[2], "v=%d", &version)
	if err != nil {
		return nil, nil, nil, err
	}
	if version != argon2.Version {
		return nil, nil, nil, errors.New(ErrIncompatibleVersion)
	}

	newArgon2Format := &Argon2Format{}
	_, err = fmt.Sscanf(vals[3], "m=%d,t=%d,p=%d", &newArgon2Format.Memory, &newArgon2Format.Iterations, &newArgon2Format.Parallelism)
	if err != nil {
		return nil, nil, nil, err
	}

	salt, err = base64.RawStdEncoding.Strict().DecodeString(vals[4])
	if err != nil {
		return nil, nil, nil, err
	}

	hash, err = base64.RawStdEncoding.Strict().DecodeString(vals[5])
	if err != nil {
		return nil, nil, nil, err
	}
	newArgon2Format.KeyLength = uint32(len(hash))

	return newArgon2Format, salt, hash, nil
}

/*
 组装前端传入的hash数组

 参数:
   hashArr:前端传入的hash数组

 返回:
   组装后的前端密码字符串
*/
// ——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
func AssemblePePassword(hashArr []byte) string {
	// Base64对盐值和散列密码进行编码。
	//----------------------------------------------------------------------------------------------------------
	b64Salt := base64.RawStdEncoding.EncodeToString([]byte(PEKEY))
	b64Hash := base64.RawStdEncoding.EncodeToString(hashArr)

	// 使用标准编码的哈希表示形式返回字符串。
	//----------------------------------------------------------------------------------------------------------
	encodedHash := fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, Argon2PE.Memory, Argon2PE.Iterations, Argon2PE.Parallelism, b64Salt, b64Hash)

	return encodedHash
}
